﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Streambolics
{
    /// <summary>
    ///     A polled computing module that processes data in chunks, each
    ///     chunk representing a payload.
    /// </summary>
    /// <remarks><para>
    ///     Payload processing is divided in three steps:
    ///     <list type="bullet"><item>
    ///         Initialization of the payload. In this step, the module determines
    ///         the work to be done. This step is always performed in the main thread
    ///         so should not block.
    ///     </item><item>
    ///         Payload processing. In this step, the module actually processes the
    ///         data of the payload and performs necessary actions. This step may be
    ///         executed in a background thread.
    ///     </item><item>
    ///         Finalization of the payload. In this step, the module finalizes the
    ///         payload, for example by cleaning up temporary data. This step is always
    ///         performed in the main thread.
    ///     </item></list>
    /// </para></remarks>

    public abstract class PayloadComputingModule : PolledComputingModule
    {
        private bool _PayloadPending;
        private bool _Asynchronous;
        private bool _Asap;
        private Exception _ThreadException;
        private Thread _Thread;

        private static SynchronizationContext _MainThread = SynchronizationContext.Current;

        /// <summary>
        ///     Whether payload processing is asynchronous.
        /// </summary>
        /// <remarks><para>
        ///     When payload processing is asynchronous, processing occurs in a background
        ///     thread.
        /// </para><para>
        ///     It is an error to change this property while payload is being processed.
        /// </para></remarks>

        public bool Asynchronous
        {
            get
            {
                return _Asynchronous;
            }
            protected set
            {
                if (_PayloadPending)
                {
                    throw new ThreadStateException ();
                }
                _Asynchronous = value;
            }
        }

        /// <summary>
        ///     Whether a new payload must be rescheduled immediately after the
        ///     previous has been processed.
        /// </summary>
        /// <remarks><para>
        ///     When this property is true, each time a payload has been processed,
        ///     a new payload is immediately scheduled, without waiting for the poll
        ///     period to elapse.
        /// </para><para>
        ///     This property is only honored for asynchronous operations.
        /// </para></remarks>

        public bool FastReschedule
        {
            get
            {
                return _Asap;
            }
            protected set
            {
                _Asap = value;
            }
        }

        #region Background thread processing

        private void PayloadThread ()
        {
            try
            {
                DoProcessPayload ();
            }
            catch (Exception e)
            {
                ReportPayloadError (e);
            }
            finally
            {
                ReportFinalizePayload ();
            }
        }

        private void ReportPayloadError (Exception e)
        {
            _ThreadException = e;
            _MainThread.Send (SyncPayloadError, e);
        }

        private void SyncPayloadError (object o)
        {
            DoPayloadError (_ThreadException);
        }

        private void ReportFinalizePayload ()
        {
            _ThreadException = null;
            _MainThread.Post (SyncFinalizePayload, null);
        }

        private void SyncFinalizePayload (object o)
        {
            DoFinalizePayload ();
        }

        #endregion

        #region Implementation of PolledComputingModule

        protected override void DoPoll ()
        {
            if (!_PayloadPending)
            {
                if (InitializePayload ())
                {
                    _PayloadPending = true;
                    if (_Asynchronous)
                    {
                        _Thread = new Thread (new ThreadStart (PayloadThread));
                        _Thread.Start ();
                    }
                    else
                    {
                        try
                        {
                            DoProcessPayload ();
                        }
                        catch (Exception e)
                        {
                            DoPayloadError (e);
                        }
                        finally
                        {
                            DoFinalizePayload ();
                        }
                    }
                }
            }
        }

        #endregion

        #region Method wrappers

        private void DoProcessPayload ()
        {
            ProcessPayload ();
        }

        private void DoPayloadError (Exception e)
        {
            try
            {
                PayloadError (e);
            }
            catch (Exception ex)
            {
                DeclareFailed (ex);
            }
        }

        private void DoFinalizePayload ()
        {
            _PayloadPending = false;
            if (IsStopping)
            {
                DeclareStopped ();
            }

            try
            {
                FinalizePayload ();
                if (_Asynchronous && _Asap && IsRunning)
                {
                    DoPoll ();
                }
            }
            catch (Exception ex)
            {
                DeclareFailed (ex);
            }
        }

        #endregion

        #region Actual payload processing

        /// <summary>
        ///     Initialize a new payload to process.
        /// </summary>
        /// <returns>
        ///     True if a new payload is ready, false otherwise.
        /// </returns>
        /// <remarks><para>
        ///     An implementation should redefine this method to fetch a new payload
        ///     to process. 
        /// </para><para>
        ///     This method is always called in the context of the main thread, so
        ///     it should never block.
        /// </para><para>
        ///     If this method throws an exception, the module will be declared failed.
        /// </para></remarks>

        protected virtual bool InitializePayload ()
        {
            return false;
        }

        /// <summary>
        ///     Process the payload retrieved by the last call to InitializePayload().
        /// </summary>
        /// <remarks><para>
        ///     This is where the module should process the data of the payload.
        /// </para><para>
        ///     If Asynchronous is true, then this method is called in the context of
        ///     a background thread. Otherwise, it is called in the context of the main
        ///     thread, and in that case should not block.
        /// </para><para>
        ///     If this method throws an exception, the PayloadError() method will be
        ///     called from the main thread.
        /// </para></remarks>

        protected virtual void ProcessPayload ()
        {
        }

        /// <summary>
        ///     Finalize the payload processed by ProcessPayload()
        /// </summary>
        /// <remarks><para>
        ///     This is where the module should perform the last cleanup
        ///     after the payload has been processed.
        /// </para><para>
        ///     This method is always called in the context of the main thread.
        ///     and should not block.
        /// </para><para>
        ///     If this method throws an exception, the module will be failed.
        /// </para></remarks>

        protected virtual void FinalizePayload ()
        {
        }

        /// <summary>
        ///     Process an error from ProcessPayload()
        /// </summary>
        /// <param name="e">
        ///     The exception that occurred in ProcessPayload()
        /// </param>
        /// <remarks><para>
        ///     This method gives a chance to the module to act on an error
        ///     that occurred in the ProcessPayload() method.
        /// </para><para>
        ///     This method is always called in the context of the main thread.
        /// </para><para>
        ///     If the method throws an exception, the module will be failed.
        /// </para><para>
        ///     The default behaviour is to re-throw the passed exception.
        /// </para></remarks>

        protected virtual void PayloadError (Exception e)
        {
            throw e;
        }

        #endregion

        protected override void DoStart ()
        {
            DeclareStarted ();
        }

        protected override void DoStop ()
        {
            if (!_PayloadPending)
            {
                DeclareStopped ();
            }
        }
    }
}
